《Android开源库》 Realm For Android~ RelationShips and Writes(译文)

关系

任何两个RealmObject都可以链接在一起。

1
2
3
4
5
6
7
8
9
10
11
public class Email extends RealmObject {
private String address;
private boolean active;
// ... setters and getters left out
}

public class Contact extends RealmObject {
private String name;
private Email email;
// ... setters and getters left out
}

关系在Realm中开销不大。这意味着保持连接并不会影响速度,并且关系的内部实现在使用内存上也是相当高效的。

多对一

在类中简单的定义一个继承自RealmObject的子类作为成员即可:

1
2
3
4
public class Contact extends RealmObject {
private Email email;
// Other fields…
}

每个联系人(Contact的实例)都有0或1个电子邮件(Email实例)。在Realm中,没有什么可以阻止你在多个联系人中使用相同的电子邮件对象,上述模型可以是多对一关系,但通常用于建模一对一关系。

设置RealmObject字段null将清除引用,但对象不会从Realm中删除

多对多

你可以通过RealmList<T>字段声明从单个对象与任意数量的对象建立关系。例如,假设有多个电子邮件地址的联系人

1
2
3
4
5
6
7
8
9
public class Contact extends RealmObject {
public String name;
public RealmList<Email> emails;
}

public class Email extends RealmObject {
public String address;
public boolean active;
}

RealmLists基本上是RealmObjects的容器,并且RealmList和一个普通的Java List用法基本相同。Realm在不同的RealmLists中可以使用相同的对象两次(或更多),没有限制,因此你可以使用它来建模一对多和多对多关系。

你可以创建对象,并使用RealmList.add()将Email对象添加到Contact对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Contact contact = realm.createObject(Contact.class);
contact.name = "John Doe";

Email email1 = realm.createObject(Email.class);
email1.address = "john@example.com";
email1.active = true;
contact.emails.add(email1);

Email email2 = realm.createObject(Email.class);
email2.address = "jd@example.com";
email2.active = false;
contact.emails.add(email2);
}
});

可以声明递归关系,当建模某些类型的数据时,这些关系可能很有用

1
2
3
4
5
public class Person extends RealmObject {
public String name;
public RealmList<Person> friends;
// Other fields…
}

将RealmList字段的值设置null将清除列表。也就是说,列表将为空(长度为零),但没有对象会被删除。通过getter方法获取RealmList将永远不会返回null。返回的对象总是一个列表,但长度可能为零。

连接查询

查询连接和关系时可以的。考虑如下模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person extends RealmObject {
private String id;
private String name;
private RealmList<Dog> dogs;
// getters and setters
}

public class Dog extends RealmObject {
private String id;
private String name;
private String color;
// getters and setters
}

每个Person可以和狗建立一对多的关系,正如下表所示:

这里写图片描述

使用连接查询来找一些人

1
2
3
4
// persons => [U1,U2]
RealmResults<Person> persons = realm.where(Person.class)
.equalTo("dogs.color", "Brown")
.findAll();

首先,请注意,条件中的字段名称”equalTo”包含通过关系的路径(以句点分隔.)。

上述查询应读取,查找到拥有名为”布朗”的狗的所有人。重要的是要理解,结果将包含不满足条件的Dog对象,因为它们是Person对象的一部分。

1
2
persons.get(0).getDogs(); // => [A,B]
persons.get(1).getDogs(); // => [B,C,D]

这可以通过以下两个查询进一步检查。

1
2
3
4
5
6
7
8
9
// r1 => [U1,U2]
RealmResults<Person> r1 = realm.where(Person.class)
.equalTo("dogs.name", "Fluffy")
.findAll();

// r2 => [U1,U2]
RealmResults<Person> r2 = r1.where()
.equalTo("dogs.color", "Brown")
.findAll();

注意第一个查询如何返回这两个Person对象,因为条件与两个人都匹配。每个Person查询结果都包含一个Dog对象列表- 它们的所有Dog对象(即使是那些不满足原始查询条件的对象)。记住,我们正在寻找有特定种类的Dog(姓名和颜色)的人,而不是实际的狗本身。因此,第二个查询将针对第一个Person查询result(r1)和每个Persons狗进行评估。第二个查询也匹配两个人,但这次是因为狗的颜色。

让我们深入一点,以帮助巩固这个概念。请查看以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/ r1 => [U1,U2]
RealmResults<Person> r1 = realm.where(Person.class)
.equalTo("dogs.name", "Fluffy")
.equalTo("dogs.color", "Brown")
.findAll();

// r2 => [U2]
RealmResults<Person> r2 = realm.where(Person.class)
.equalTo("dogs.name", "Fluffy")
.findAll()
.where()
.equalTo("dogs.color", "Brown")
.findAll();
.where()
.equalTo("dogs.color", "Yellow")
.findAll();

第一个查询应该读取,查找到所有有名为”Fluffy”狗的 Person,也找到那些有名为”Brown”狗的Person,然后给我两个的​​交集。第二个查询先读取并查找到所有名为“Fluffy”的狗的Person。然后,给定结果集,找到所有有颜色为“棕色”的狗的Person,并且给定结果集,找到具有颜色为“黄色”的狗的所有人。

让我们来看看背后的查询,r1以便充分了解发生了什么。两个条件是equalTo(“dogs.name”, “Fluffy”)和equalTo(“dogs.color”, “Brown”)。第一个条件满足U1和U2- 这是C1集合。第二个条件也满足U1和U2- 这是C2集合。查询中的逻辑和,和C1和C2的交集是一样的。C1和C2的交集为U1 and U2。因此,r1是U1 and U2。

后面的查询r2就不同了。让我们开始分解这次查询以探究竟。查询的第一部分如下所示:RealmResults<Person> r2a = realm.where(Person.class).equalTo(“dogs.name”, “Fluffy”).findAll();。它匹配U1和U2。然后,r2b = r2a.where().equalTo(“dogs.color”, “Brown”).findAll();还是匹配U1和U2(两个人都有棕色的狗)。最后的查询,r2 = r2b.where().equalTo(“dogs.color”, “Yellow”).findAll();只匹配U2,因为只有一个人在棕色狗结果集中有一个黄色的狗,也就是U2

读操作是隐式的,这意味着可以随时访问和查询对象。所有写操作(添加,修改和删除对象)必须包含在写事务中。写事务可以提交或取消。在提交期间,所有更改都将写入磁盘,并且提交只有在所有更改都可以保留时才会成功。通过取消写入事务,所有更改都将被丢弃。使用写事务,你的数据将始终处于一致状态。

写事务也用于确保线程安全

1
2
3
4
5
6
7
8
// Obtain a Realm instance
Realm realm = Realm.getDefaultInstance();

realm.beginTransaction();

//... add or update objects here ...

realm.commitTransaction();

在写事务中使用到你的RealmObjects时,你可能最后想放弃修改。与其提交之后再回退,你可以简单地取消写事务。

1
2
3
4
5
6
realm.beginTransaction();
User user = realm.createObject(User.class);

// ...

realm.cancelTransaction();

请注意,写事务可能会相互阻塞。如果你在UI线程和后台线程上同时创建写入事务,这可能会导致ANR错误。为了避免这种情况,在UI线程上创建写事务时使用异步事务。

Realm是崩溃安全的,所以在一次事务中发生了Exception,Realm本身不会崩溃。只是当前事务中的数据会丢失。如果异常被捕获但应用程序仍在继续,这个时候取消正在执行的事务就显得尤为重要。如果使用executeTransaction(),这些将会自动的执行。

感谢Realm的MVCC架构,在写事务打开的时候读取不会被阻塞!这意味着,除非你需要从多个线程同时进行事务,你可以使用一个更大的事务,这些事务如果使用小事务来做的话将会耗费更多的工作量。当你将写事务提交到Realm时,该Realm的所有其他实例将被通知并自动更新

Realm中的读写访问是ACID

创建对象

因为RealmObjects都得绑定到一个领域,他们应该通过Realm直接实例化

1
2
3
4
5
realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("john@corporation.com");
realm.commitTransaction();

或者,你可以首先创建一个对象的实例,然后使用realm.copyToRealm()添加它。Realm支持尽可能多的自定义构造函数,只要其中一个是公共的无参数构造函数

1
2
3
4
5
6
7
User user = new User("John");
user.setEmail("john@corporation.com");

// Copy the object to Realm. Any further changes must happen on realmUser
realm.beginTransaction();
User realmUser = realm.copyToRealm(user);
realm.commitTransaction();

当使用时,realm.copyToRealm()重要的是要记住只有返回的对象由Realm管理,所以对原始对象的任何进一步的更改都不会被持久化

事务块

不用手动保持跟踪realm.beginTransaction(),realm.commitTransaction()以及realm.cancelTransaction()可以使用realm.executeTransaction()方法,它会自动处理开始/提交,发生错误的时候将自动取消

1
2
3
4
5
6
7
8
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
});

异步事务

因为事务被其他事务阻塞,所以在后台线程上执行所有写操作以避免阻塞UI线程是一个很好的主意。通过使用异步事务,Realm将在后台线程上运行该事务并在事务完成时报告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// Transaction was a success.
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
}
});

OnSuccess和OnError回调都是可选的,但是如果提供,它们将在事务成功完成或失败时分别被调用。回调由Looper控制,因此它们只允许在Looper线程上回调。

1
2
3
4
5
6
7
8
ealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
}, null);

异步事务由RealmAsyncTask对象表示。如果在事务完成之前退出Activity / Fragment,此对象可用于取消任何挂起的事务。如果回调更新UI,忘记取消事务可能会导致应用程序崩溃。

1
2
3
4
5
ublic void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}

更新字符串和字节数

Realm是以完整的字段作为操作对象的,不可能更新字符串或字节数组的单个元素。假如你需要更新字符串的第5个元素,你将需要做类似的事情:

1
2
3
4
5
6
7
8
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
bytes[] bytes = realmObject.binary;
bytes[4] = 'a';
realmObject.binary = bytes;
}
});

是由于Realm的MVCC架构,它避免了对现有数据的改变,以确保读取数据的其他线程或进程下表现一致。

快照

所有Realm集合都是实时的。这意味着它们总是反映最新的状态。在大多数情况下,这是可取的,但如果你循环一个集合,目的是修改元素呢?例如:

1
2
3
4
5
6
RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll();
realm.beginTransaction();
for (int i = 0; guests.size(); i++) {
guests.get(i).setInvited(true);
}
realm.commitTransaction();

通常你会期望这个简单的循环邀请所有客人。因为RealmResults是立即更新,虽然,只有一半的客人最终被邀请!受邀的邀请对象将从该集合中立即删除,这会移动所有元素。当i参数增加时,它将错过一个元素。

为了防止这种情况,你可以拍摄集合数据的快照。快照保证元素的顺序不会改变,即使元素被删除或修改。

Iterator从Realm集合创建的文件将自动使用快照。RealmResults和RealmList可以使用createSnapshot()手动创建快照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll();

// Use an iterator to invite all guests
realm.beginTransaction();
for (Person guest : guests) {
guest.setInvited(true);
}
realm.commitTransaction();

// Use a snapshot to invite all guests
realm.beginTransaction();
OrderedRealmCollectionSnapshot<Person> guestsSnapshot = guests.createSnapshot();
for (int i = 0; guestsSnapshot.size(); i++) {
guestsSnapshot.get(i).setInvited(true);
}
realm.commitTransaction();

原文链接

https://realm.io/docs/java/latest/#relationships

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×